package com.company.game01;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.setting.dialect.Props;

import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import static com.company.game01.MyUtil.*;

/**
 * 地图
 */
public class GameMap {
    private static final int X = Tank.RADIUS * 2; // 地图坐标
    private static final int Y = Tank.RADIUS * 4 + MyFrame.TOP;
    private static final int WIDTH = FRAME_WIDTH - Tank.RADIUS * 2; // 地图尺寸
    private static final int HEIGHT = FRAME_HEIGHT - Tank.RADIUS * 4 - MyFrame.TOP;

    public static final int HOUSE_X = FRAME_WIDTH - MapTile.TILE_W * 3 >> 1;
    public static final int HOUSE_Y = FRAME_HEIGHT - (MapTile.TILE_W << 1);

    private List<MapTile> tiles = new LinkedList<>();
    private static final ObjectPool<MapTile> OBJECT_POOL = new ObjectPool<>(MapTile.class);

    public GameMap(int level) {
        initMap(level);
    }

    public void initMap(int level) {
//        addRow(X, Y, X + WIDTH - MapTile.TILE_W, MapTile.TYPE_NORMAL, 0);
//        addRow(X, Y + (MapTile.TILE_W << 1), X + WIDTH - MapTile.TILE_W, MapTile.TYPE_COVER, 0);
//        addRow(X, Y + (MapTile.TILE_W << 2), X + WIDTH - MapTile.TILE_W, MapTile.TYPE_HARD, Tank.RADIUS << 1);
//        addCol(X, Y, Y + HEIGHT - MapTile.TILE_W, MapTile.TYPE_NORMAL, 0);
//        addCol(X + 2 * MapTile.TILE_W, Y, Y + HEIGHT - MapTile.TILE_W, MapTile.TYPE_NORMAL, 0);
//        addRect(X, Y, X + WIDTH - MapTile.TILE_W, Y + HEIGHT - MapTile.TILE_W, MapTile.TYPE_NORMAL, 0);
        int count = 50;
        int[] types = {MapTile.TYPE_NORMAL, MapTile.TYPE_COVER, MapTile.TYPE_HARD};
        if (level < 4) {
            try {
                loadLevel(level);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            for (int j = 0; j < count; j++) {
                int x = RandomUtil.randomInt(X, X + WIDTH - (MapTile.TILE_W << 1));
                int y = RandomUtil.randomInt(Y, Y + HEIGHT - (MapTile.TILE_W << 2));
                if (isCollide(tiles, x, y)) {
                    j--;
                    continue;
                }
                MapTile tile = OBJECT_POOL.get();
                tile.setX(x);
                tile.setY(y);
                tile.setType(types[RandomUtil.randomInt(types.length)]);
                tile.setVisible(true);
                tiles.add(tile);
            }
        }
        addHouse();
    }

    private void loadLevel(int level) throws InvocationTargetException, IllegalAccessException {
        Props props = Props.getProp("level/level" + level + ".properties");
        int enemyCount = props.getInt("enemyCount");
        String method = props.getStr("method");
        Method m = ReflectUtil.getMethodByName(this.getClass(), method);
        int invokeCount = props.getInt("invokeCount");
        for (int i = 0; i < invokeCount; i++) {
            String[] params = props.getStr("param" + (i + 1)).split(",");
            Object[] args = new Object[params.length];
            if ("addRow".equals(method)) {
                args[2] = X + WIDTH - MapTile.TILE_W;
            } else if ("addCol".equals(method)) {
                args[2] = Y + HEIGHT - MapTile.TILE_W;
            } else if ("addRect".equals(method)) {
                args[0] = X + (int) (Double.parseDouble(params[0]) * (Tank.RADIUS << 1));
                args[1] = Y;
                args[2] = X + WIDTH - MapTile.TILE_W;
                args[3] = Y + HEIGHT - MapTile.TILE_W;
                args[4] = Integer.parseInt(params[4]);
                args[5] = (int) (Double.parseDouble(params[5]) * (Tank.RADIUS << 1));
                m.invoke(this, args);
                continue;
            }
            args[0] = X + Integer.parseInt(params[0]) * ((Tank.RADIUS << 1) + 1);
            args[1] = Y + Integer.parseInt(params[1]) * MapTile.TILE_W;
            args[3] = Integer.parseInt(params[3]);
            args[4] = (int) (Double.parseDouble(params[4]) * (Tank.RADIUS << 1));
            m.invoke(this, args);
        }
    }

    /**
     * 添加一行地图元素
     *
     * @param startX
     * @param y
     * @param endX
     * @param type   元素类型
     * @param dis    元素间隔，dis = 0 表示连续。
     */
    private void addRow(int startX, int y, int endX, int type, int dis) {
        int count = (endX - startX) / (MapTile.TILE_W + dis);
        for (int i = 0; i < count; i++) {
            MapTile tile = OBJECT_POOL.get();
            tile.setX(startX + i * (MapTile.TILE_W + dis));
            tile.setY(y);
            tile.setType(type);
            tile.setVisible(true);
            tiles.add(tile);
        }
    }

    /**
     * 添加一列地图元素
     *
     * @param x
     * @param startY
     * @param endY
     * @param type   元素类型
     * @param dis    元素间隔，dis = 0 表示连续。
     */
    private void addCol(int x, int startY, int endY, int type, int dis) {
        int count = (endY - startY) / (MapTile.TILE_W + dis);
        for (int i = 0; i < count; i++) {
            MapTile tile = OBJECT_POOL.get();
            tile.setX(x);
            tile.setY(startY + i * (MapTile.TILE_W + dis));
            tile.setType(type);
            tile.setVisible(true);
            tiles.add(tile);
        }
    }

    private void addRect(int startX, int startY, int endX, int endY, int type, int dis) {
        int rows = (endY - startY) / (MapTile.TILE_W + dis);
        for (int i = 0; i < rows; i++) {
            addRow(startX, startY + i * (MapTile.TILE_W + dis), endX, type, dis);
        }
    }

    private void addHouse() {
        MapTile tile;
        tile = OBJECT_POOL.get();
        tile.setX(HOUSE_X);
        tile.setY(HOUSE_Y);
        tile.setType(MapTile.TYPE_NORMAL);
        tile.setVisible(true);
        tiles.add(tile);

        tile = OBJECT_POOL.get();
        tile.setX(HOUSE_X + MapTile.TILE_W);
        tile.setY(HOUSE_Y);
        tile.setType(MapTile.TYPE_NORMAL);
        tile.setVisible(true);
        tiles.add(tile);

        tile = OBJECT_POOL.get();
        tile.setX(HOUSE_X + (MapTile.TILE_W << 1));
        tile.setY(HOUSE_Y);
        tile.setType(MapTile.TYPE_NORMAL);
        tile.setVisible(true);
        tiles.add(tile);

        tile = OBJECT_POOL.get();
        tile.setX(HOUSE_X + (MapTile.TILE_W << 1));
        tile.setY(HOUSE_Y + MapTile.TILE_W);
        tile.setType(MapTile.TYPE_NORMAL);
        tile.setVisible(true);
        tiles.add(tile);

        tile = OBJECT_POOL.get();
        tile.setX(HOUSE_X);
        tile.setY(HOUSE_Y + MapTile.TILE_W);
        tile.setType(MapTile.TYPE_NORMAL);
        tile.setVisible(true);
        tiles.add(tile);

        tile = OBJECT_POOL.get();
        tile.setX(HOUSE_X + MapTile.TILE_W);
        tile.setY(HOUSE_Y + MapTile.TILE_W);
        tile.setType(MapTile.TYPE_HOUSE);
        tile.setVisible(true);
        tiles.add(tile);
    }

    public void draw(Graphics g) {
        for (int i = 0; i < tiles.size(); i++) {
            MapTile tile = tiles.get(i);
            if (tile.isVisible()) {
                if (tile.getType() != MapTile.TYPE_COVER)
                    tile.draw(g);
            } else {
                tiles.remove(tile);
                OBJECT_POOL.ret(tile);
            }
        }
    }

    public void drawCover(Graphics g) {
        for (int i = 0; i < tiles.size(); i++) {
            MapTile tile = tiles.get(i);
            if (tile.getType() == MapTile.TYPE_COVER)
                tile.draw(g); // 后绘制遮挡元素
        }
    }

    /**
     * 判断元素是否重叠
     */
    private boolean isCollide(List<MapTile> tiles, int x, int y) {
        for (MapTile tile : tiles) {
            if (isTouch(tile.getX(), tile.getY(), MapTile.TILE_W, x, y)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断子弹击中元素
     */
    public void isHitTiles(List<Bullet> bullets) {
        for (Bullet bullet : bullets) {
            for (MapTile tile : tiles) {
                if (bullet.getRect().intersects(tile.getRect())) {
                    switch (tile.getType()) {
                        case MapTile.TYPE_NORMAL:
                            bullet.setVisible(false);
                            tile.setVisible(false);
                            break;
                        case MapTile.TYPE_HOUSE:
                            MyFrame.BOOMS.add(new Boom(tile.getX() + 10, tile.getY()));
                            bullet.setVisible(false);
                            tile.setVisible(false);
                            ThreadUtil.execute(() -> {
                                ThreadUtil.sleep(3000);
                                MyFrame.game_state = GAME_STATE_LOST;
                            });
                            return;
                        case MapTile.TYPE_COVER:
                            break;
                        case MapTile.TYPE_HARD:
                            bullet.setVisible(false);
                            break;
                    }
                }
            }
        }
    }

    /**
     * 判断坦克碰撞砖块
     */
    public void isCollideTank(Tank tank) {
        for (MapTile tile : tiles) {
            if (tile.getType() == MapTile.TYPE_COVER) continue;
            if (tank.getRect().intersects(tile.getRect())) {
                tank.back();
            }
        }
    }
}

/**
 * 地图元素块
 */
class MapTile {
    private static final Image[] IMAGES = {
            getImage("/static/tile.png"),
            getImage("/static/house.gif"),
            getImage("/static/cover.gif"),
            getImage("/static/hard.gif")
    };
    public static final int TILE_W = 40;
    public static final int TYPE_NORMAL = 0;
    public static final int TYPE_HOUSE = 1;
    public static final int TYPE_COVER = 2;
    public static final int TYPE_HARD = 3;

    private int x, y, type;
    private boolean visible = true;

    public MapTile() {
    }

    public void draw(Graphics g) {
        g.drawImage(IMAGES[type], x, y, null);
    }

    public Rectangle getRect() {
        return new Rectangle(x, y, TILE_W, TILE_W);
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }
}
